/*
 * @(#) $Header$
 *
 * Copyright (C) 2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.util.iterator;

import java.util.Collection;
import java.util.Iterator;
import ca.forklabs.baselib.util.iterator.IterableIterator;
import ca.forklabs.baselib.util.iterator.Resources;

import java.util.NoSuchElementException;

/**
 * Class {@code RangedIterator} is a <em>forward</em> <em>non-alterable</em> and
 * <em>non-appendable</em> iterator used as an adapter to allow an
 * {@link Iterator} to return at most a pre-determined number of elements from a
 * bigger {@link Collection}.
 *
 * @param   <E>   the type of the elements.
 *
 * @author <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.util.iterator.RangedIterator">Daniel Léonard</a>
 * @version $Revision$
 */
public class RangedIterator<E> extends IterableIterator<E> {

//---------------------------
// Instance variable
//---------------------------

   /** The size of the range. The number of elements left to return. */
   private int elements_left;

   /** The adapted iterator. */
   private Iterator<E> adaptee;


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructs and readies a {@code RangedIterator}.
    * @param   adaptee   the adapted iterator.
    * @param   range   the maximum number of elements to return.
    * @exception   NullPointerException   if {@code adaptee} is
    *                                     {@code null}.
    * @exception   IllegalArgumentException   if the {@code range} is
    *                                         smaller than zero.
    */
   public RangedIterator(Iterator<E> adaptee, int range) {
      this.setIterator(adaptee);
      this.setRange(range);
      }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Changes the adapted iterator.
    * @param   adaptee   the adapted iterator.
    * @exception   NullPointerException   if the parameter is {@code null}.
    */
   protected void setIterator(Iterator<E> adaptee) {
      if (null == adaptee) {
         String message = this.getCannotAdaptIteratorErrorMessage();
         throw new NullPointerException(message);
         }
      this.adaptee = adaptee;
      }

   /**
    * Returns the adapted iterator.
    * @return   the adapted iterator.
    */
   protected Iterator<E> getIterator() {
      return this.adaptee;
      }

   /**
    * Changes the range of the iterator.
    * @param   range   the new range.
    * @exception   IllegalArgumentException   if the parameter is smaller than
    *                                         zero.
    */
   protected void setRange(int range) {
      if (range < 0) {
         String message = this.getBadRangeErrorMessage(range);
         throw new IllegalArgumentException(message);
         }
      this.elements_left = range;
      }

   /**
    * Returns the number of element left to return.
    * @return   the number of elements left to return.
    */
   protected int getRange() {
      return this.elements_left;
      }


//---------------------------
// Implemented methods from java.util.Iterator
//---------------------------

   /**
    * Returns {@code true} if the iteration has more elements. The iteration has
    * more elements if the underlying iteration has more elements and if the end
    * of the range of this iteration has not yet been reached.
    * @return   {@code true} if the iterator has more elements,
    *           {@code false} otherwise.
    */
   @Override
   public boolean hasNext() {
      boolean has_next = false;

      int left = this.getRange();
      if (0 < left) {
         has_next = true;
         }

      if (has_next) {
         Iterator<E> iter = this.getIterator();
         has_next = iter.hasNext();
         }

      return has_next;
      }

   /**
    * Returns the next element in the iteration. It calls {@code next()} on the
    * underlying iterator.
    * @return   the next element in the iteration.
    * @exception   NoSuchElementException   if the underlying iteration has no
    *                                       more elements.
    * @exception   NoSuchElementException   if the range has been covered.
    */
   @Override
   public E next() {
      E element = null;

      boolean proceed = this.hasNext();
      if (false == proceed) {
         int left = this.getRange();
         boolean adaptee_failed = (0 < left);
         String message = (adaptee_failed) ? this.getAdaptedIteratorHasNoMoreElementsErrorMessage() : this.getRangeExceeded();
         throw new NoSuchElementException(message);
         }

      Iterator<E> iter = this.getIterator();
      element = iter.next();

      int left = this.getRange();
      left--;
      this.setRange(left);

      return element;
      }

   /**
    * Unsupported operation.
    * @exception   UnsupportedOperationException   always.
    */
   @Override
   public void remove() {
      String message = this.getRemoveNotSupportedErrorMessage();
      throw new UnsupportedOperationException(message);
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets the error message telling that it is impossible to adapt the
    * iterator.
    * @return   the formatted message.
    */
   protected String getCannotAdaptIteratorErrorMessage() {
      String key = Resources.ITERATOR_CANNOT_ADAPT;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message telling that the range is negative.
    * @param   range   the bad range.
    * @return   the formatted message.
    */
   @SuppressWarnings("boxing")
   protected String getBadRangeErrorMessage(int range) {
      String key = Resources.RANGED_ITERATOR_BAD_RANGE;
      String message = Resources.getLocalizedString(key, range);
      return message;
      }

   /**
    * Gets the error message telling that {@link #remove()} is not supported.
    * @return   the formatted message.
    */
   @SuppressWarnings("nls")
   protected String getRemoveNotSupportedErrorMessage() {
      String key = Resources.ITERATOR_UNSUPPORTED_METHOD;
      String method = "remove()";
      String message = Resources.getLocalizedString(key, method);
      return message;
      }

   /**
    * Gets the error message telling that the adapted iterator does not have any
    * more element.
    * @return   the formatted message.
    */
   protected String getAdaptedIteratorHasNoMoreElementsErrorMessage() {
      String key = Resources.RANGED_ITERATOR_ADAPTED_NO_MORE_ELEMENTS;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   /**
    * Gets the error message telling that the range has been exceeded.
    * @return   the formatted message.
    */
   protected String getRangeExceeded() {
      String key = Resources.RANGED_ITERATOR_RANGE_EXCEEDED;
      String message = Resources.getLocalizedString(key);
      return message;
      }

   }
